package com.gitee.vueantd.upload.service.impl;

import com.gitee.vueantd.common.config.FileProperties;
import com.gitee.vueantd.common.utils.DefaultThreadFactory;
import com.gitee.vueantd.common.utils.ElAdminConstant;
import com.gitee.vueantd.upload.repository.UploadChunkRepository;
import com.gitee.vueantd.upload.service.UploadService;
import com.gitee.vueantd.upload.service.UploadTaskDataService;
import com.gitee.vueantd.upload.service.dto.ChunkRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.util.concurrent.*;

/**
 * @ClassName: UploadServiceImpl
 * @Description: TODO
 * @Author: fuqiang zheng
 * @Date: 2021/4/30 10:18
 * @Version 1.0
 */
@Slf4j
@Service
public class UploadServiceImpl implements UploadService {

  private BlockingQueue queue = new LinkedBlockingQueue(100);
  private ExecutorService executor = new ThreadPoolExecutor(10, 10
          , 60, TimeUnit.SECONDS, queue, new DefaultThreadFactory("upload-helper-pool"));

  @Autowired
  private FileProperties fileProperties;

  @Autowired
  private UploadChunkRepository uploadChunkRepository;

  @Autowired
  private UploadTaskDataService uploadTaskDataService;


  @Override
  public void upload(HttpServletRequest request) throws Exception {
    long start = System.currentTimeMillis();
    ChunkRequest chunkRequest = new ChunkRequest(request);
    File file = getUploadChunkParent(chunkRequest.fileHash, chunkRequest.fileType);
    long contentLength = request.getContentLength();
    byte[] bytes = new byte[ElAdminConstant.UPLOAD_BUFFER_SIZE];
    writeChunk(request, bytes, chunkRequest, file, contentLength);
    log.info("write file: {}, index: {}, cost: {}", chunkRequest.getFileHash(), chunkRequest.getChunkIndex(), System.currentTimeMillis() - start);

    FileUploadCallable callable = new FileUploadCallable(uploadTaskDataService, uploadChunkRepository, chunkRequest, file, contentLength);
    if (chunkRequest.chunkIndex.equals(chunkRequest.totalChunks)) { // 结束，不异步
      log.info("reach to upload end, file: {}", chunkRequest.getFileHash());
      callable.call();
    } else {
      executor.submit(callable);
    }
  }

  private File getUploadChunkParent(String fileName, int fileType) {
    return FileUtils.getFile(getUploadFolder(fileType), fileName);
  }

  private File getUploadFolder(Integer fileType) {
    File file = FileUtils.getFile(fileProperties.getPath().getPath(), String.valueOf(fileType));
    file.mkdirs();
    return file;
  }

  private void writeChunk(HttpServletRequest request, byte[] bytes, ChunkRequest chunkRequest, File file, long contentLength) {
    try (InputStream inputStream = request.getInputStream();
         RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw")) {
      randomAccessFile.seek((chunkRequest.chunkIndex - 1) * chunkRequest.chunkSize);
      long readed = 0;
      while (readed < contentLength) {
        int r = inputStream.read(bytes);
        if (r < 0) {
          break;
        }
        randomAccessFile.write(bytes, 0, r);
        readed += r;
      }
    } catch (Exception e) {
      log.error("上传文件{}写入异常{}", file.getName(), e);
    }
  }

}